/*+***********************************************************************************
 Filename: 9k_mcu02_mycore02\src\core\zh_core_v02.v
 Description: demo the construction of a simple tiny-core.
              with ROM, RAM and peripheral. No interrupt.
              
 Modification:
   2022.10.13 Creation   H.Zheng  (03_tinycore_step02\src\core.v)
   ...
   2025.08.15 porting to this project (9k_mcu01_mycore_v001 zh_core_v01.v)
              implement OP_IMM, OP, JAL, BRANCH group only.
   2025.08.16 add RAM and Peripheral, LOAD/STORE group. (9k_mcu01_mycore_v01)
              change rom to dual-port rom. add bus multiplexer.
   2025.08.18 add jtag halt port (9k_mcu02_mycore01\zh_core_v011.v)
   2025.08.30 add csr and interrupt  (9k_mcu02_mycore02\zh_core_v02.v)

Copyright (C) 2022-2025  Zheng Hui (hzheng@gzhu.edu.cn)

License: MulanPSL-2.0

***********************************************************************************-*/


module zh_core_v02 (
  input wire clk,
  input wire rst_n,
  //instruction ROM
  output wire [31:0] ibus_addr,
  input wire [31:0] instruction_i,
  //data RAM & peripheral
  output wire core_loadstore_ce, //chip enable
  output wire core_loadstore_we, //write enable
  output wire [31:0] core_loadstore_addr, //address bus
  input wire [31:0] core_loadstore_data_in, //data_in,
  output wire [31:0] core_loadstore_data_out, //data_out,
  output wire [3:0] core_loadstore_wmask, //data_out byte mask
  //
  input wire halt_req_i,  //halt request
  input wire ext_int_req_i, //external interrupt request
  //
  output wire [31:0] monitor_port,
  output wire [31:0] o_ext_csr0
);


  wire reset_n = rst_n;


  //reset valid signal
  reg reset_valid;
  always @(posedge clk or negedge reset_n) begin
    if (~reset_n) begin
      reset_valid <= 1'b1;
    end
    else begin
      reset_valid <= 1'b0;
    end
  end    

  //halt valid signal
  reg halt_valid;
  always @(posedge clk) begin
    halt_valid <= halt_req_i;
  end

  //PC
  reg [31:0] program_counter; //pc_current
  wire [31:0] pc_next;
  wire [31:0] pc_next_with_int;

  assign ibus_addr = (reset_valid) ? 32'b0 :
//                      halt_valid ? program_counter : pc_next;
                      halt_valid ? program_counter : pc_next_with_int;

  always @(posedge clk) begin
    if (reset_valid)
      program_counter <= 32'hfffffffc;
    else
//      program_counter <= pc_next;
      program_counter <= ibus_addr;
  end


  //lock instruction via pos edge of core_clk
  reg [31:0] instruction;
  always @(posedge clk) begin
//    if (reset_valid)
    if (reset_valid | halt_valid)
      instruction <= {30'b0, 2'b11}; //NOP
    else
      instruction <= instruction_i;
  end



  //decode from instruction
  wire [4:0] i_rd_idx = instruction[11:7];
  wire [4:0] i_rs1_idx = instruction[19:15];
  wire [4:0] i_rs2_idx = instruction[24:20]; 
  
  //fetch rs1&rs2 value from Register File
  wire[31:0] rf_rs1data;
  wire[31:0] rf_rs2data;
  wire rf_rdwen;
  wire[31:0] rf_rd_data;
  wire[31:0] x16_value;

  //
  //Register File
  //    
//  wire rf_rdwen_active = rf_rdwen & (~reset_valid);
  wire rf_rdwen_active = rf_rdwen & (~reset_valid) & (~halt_valid);

  regfile m_regfile(
    .reset_n(reset_n),
    .i_rf_rs1idx(i_rs1_idx),
    .i_rf_rs2idx(i_rs2_idx),
    .rf_rs1data(rf_rs1data),
    .rf_rs2data(rf_rs2data),
    .i_rf_rdidx(i_rd_idx),
    .i_rf_rdwen(rf_rdwen_active),
    .i_rf_rd_data(rf_rd_data),
    .i_rf_rd_wr_clk(clk), //rd write back clk
    .x16_value(x16_value)
  );
    
  //
  //decoder
  //

  wire [6:0] opcode = instruction[6:0];
  wire [2:0] funct3 = instruction[14:12];
  wire [6:0] funct7 = instruction[31:25];   
  wire [11:0] funct12 = instruction[31:20];   

  wire opcode_OP_IMM = (opcode == 7'b0010011);
  wire opcode_OP     = (opcode == 7'b0110011);
  wire opcode_JAL    = (opcode == 7'b1101111);
  wire opcode_BRANCH = (opcode == 7'b1100011);
  wire opcode_SYSTEM = (opcode == 7'b1110011);

  wire [31:0] i_imm_J = {{12{instruction[31]}},instruction[19:12],instruction[20],instruction[30:21],1'b0};
  wire [31:0] i_imm_I = {{21{instruction[31]}},instruction[30:20]};
  wire [31:0] i_imm_U = {instruction[31:12], 12'b0};
  wire [31:0] i_imm_B = {{20{instruction[31]}},instruction[7],instruction[30:25],instruction[11:8],1'b0};
  wire [31:0] i_imm_S = {{21{instruction[31]}},instruction[30:25],instruction[11:7]};
  
  wire [31:0] rs1_plus_imm_i = rf_rs1data + i_imm_I;

  //
  //execution of OP_IMM&OP: add & sub
  //ADDI, ADD,
  //SLTI, SLTIU, SUB, SLT, SLTU
  wire inst_ADDI = opcode_OP_IMM & (funct3 == 3'b000);
  wire inst_ADD = opcode_OP & (funct3 == 3'b000)& (funct7 == 7'b0000000);
  wire inst_SUB = opcode_OP & (funct3 == 3'b000)& (funct7 == 7'b0100000);
  wire inst_SLTI = opcode_OP_IMM & (funct3 == 3'b010);
  wire inst_SLTIU = opcode_OP_IMM & (funct3 == 3'b011);
  wire inst_SLT = opcode_OP & (funct3 == 3'b010)& (funct7 == 7'b0000000);
  wire inst_SLTU = opcode_OP & (funct3 == 3'b011)& (funct7 == 7'b0000000);
  //branch inst also relie on sub operation, and BLT & BGE with signed
  wire inst_BLT = opcode_BRANCH & (funct3 == 3'b100);
  wire inst_BGE = opcode_BRANCH & (funct3 == 3'b101);    
  //add/sub operation
  wire [32:0] adder_in1_signed;
  wire [32:0] adder_in2_signed;
  wire [32:0] alu_sum_result_0;

  wire i_alu_unsigned_flag = ~(inst_SLTI | inst_SLT | inst_BLT | inst_BGE);
  wire add_flag = (inst_ADDI |inst_ADD);
  wire compare_flag = (inst_SLTI | inst_SLTIU | inst_SLT | inst_SLTU);
  wire sub_flag = (inst_SUB | compare_flag);
  wire alu_sum_flag = (add_flag | sub_flag);
  wire sub_flag_ext = (sub_flag | opcode_BRANCH);
  
  wire [31:0] sum_data_b = (inst_ADDI | inst_SLTI | inst_SLTIU) ? i_imm_I :rf_rs2data;
               
  assign adder_in1_signed = {{(~i_alu_unsigned_flag) & rf_rs1data[31]},rf_rs1data};
  assign adder_in2_signed = {{(~i_alu_unsigned_flag) & sum_data_b[31]},sum_data_b};

  assign alu_sum_result_0 = (add_flag) ? (adder_in1_signed + adder_in2_signed) :
                            (sub_flag_ext) ? (adder_in1_signed - adder_in2_signed) : 33'b0;

  wire less_flag = alu_sum_result_0[32];

  //add/sub/compare result
  wire [31:0] alu_sum_result = (~compare_flag)? alu_sum_result_0[31:0] : {31'b0, less_flag};

  
  //execution of OP_IMM&OP: shift
  //SLLI, SRLI, SRAI,
  //SLL, SRL, SRA
  wire inst_SLLI = opcode_OP_IMM & (funct3 == 3'b001)& (funct7 == 7'b0000000);
  wire inst_SRLI = opcode_OP_IMM & (funct3 == 3'b101)& (funct7 == 7'b0000000);
  wire inst_SRAI = opcode_OP_IMM & (funct3 == 3'b101)& (funct7 == 7'b0100000);
  wire inst_SLL = opcode_OP & (funct3 == 3'b001)& (funct7 == 7'b0000000);
  wire inst_SRL = opcode_OP & (funct3 == 3'b101)& (funct7 == 7'b0000000);
  wire inst_SRA = opcode_OP & (funct3 == 3'b101)& (funct7 == 7'b0100000);

  wire shift_imm_flag =  (inst_SLLI | inst_SRLI | inst_SRAI);
  wire shift_reg_flag =  (inst_SLL | inst_SRL | inst_SRA);
  wire alu_shift_flag = shift_imm_flag | shift_reg_flag;
  
  wire [4:0] i_shamt = i_rs2_idx;
  wire [4:0] shift_value = shift_imm_flag ? i_shamt :
                    shift_reg_flag ? rf_rs2data[4:0] : 5'b0;

  wire [31:0] sll_result = rf_rs1data << shift_value;
  wire [31:0] srl_result = rf_rs1data >> shift_value;
  wire [31:0] sra_mask=({32{1'b1}}) >> shift_value;
  wire [31:0] sra_result = (srl_result&sra_mask) | ({32{rf_rs1data[31]}} & (~sra_mask));

  wire [31:0] alu_shift_result = ({32{(inst_SLLI|inst_SLL)}} & sll_result) 
                         | ({32{(inst_SRLI|inst_SRL)}} & srl_result) 
                         | ({32{(inst_SRAI|inst_SRA)}} & sra_result); 

  //logical operation
  wire inst_XORI = opcode_OP_IMM & (funct3 == 3'b100);
  wire inst_ORI = opcode_OP_IMM & (funct3 == 3'b110);
  wire inst_ANDI = opcode_OP_IMM & (funct3 == 3'b111);
  wire inst_XOR = opcode_OP & (funct3 == 3'b100)& (funct7 == 7'b0000000);
  wire inst_OR = opcode_OP & (funct3 == 3'b110)& (funct7 == 7'b0000000);
  wire inst_AND = opcode_OP & (funct3 == 3'b111)& (funct7 == 7'b0000000);
  wire logical_imm_flag = (inst_XORI | inst_ORI | inst_ANDI);
  wire logical_reg_flag = (inst_XOR | inst_OR | inst_AND);
  wire alu_logical_flag = (logical_imm_flag | logical_reg_flag);
  wire [31:0] logical_data_b = (logical_imm_flag) ? i_imm_I :rf_rs2data;
    
  wire [31:0] or_result = rf_rs1data | logical_data_b;
  wire [31:0] and_result = rf_rs1data & logical_data_b;
  wire [31:0] xor_result = rf_rs1data ^ logical_data_b;

  wire [31:0] alu_logical_result = ({32{(inst_ORI|inst_OR)}} & or_result) 
                         | ({32{(inst_ANDI|inst_AND)}} & and_result) 
                         | ({32{(inst_XORI|inst_XOR)}} & xor_result); 

  //flags for branch
  assign equal_flag = (rf_rs1data == rf_rs2data);

  assign greater_equal_flag = (~less_flag);
 
  //ALU result
  wire [31:0] alu_result = alu_sum_flag ? alu_sum_result :
              alu_shift_flag ? alu_shift_result :
              alu_logical_flag ? alu_logical_result : 32'b0;

  /**
   *  Jump & Branch
   */
  wire inst_BEQ = opcode_BRANCH & (funct3 == 3'b000);
  wire inst_BNE = opcode_BRANCH & (funct3 == 3'b001);

  wire inst_BLTU = opcode_BRANCH & (funct3 == 3'b110);
  wire inst_BGEU = opcode_BRANCH & (funct3 == 3'b111);

  wire inst_MRET = opcode_SYSTEM & (funct3 == 3'b000) & (funct12 == 12'b001100000010);


  wire branch_in_effect = (inst_BEQ & (equal_flag)) | (inst_BNE & (~equal_flag)) |
                        ((inst_BLTU | inst_BLT) &  less_flag) | 
                        ((inst_BGEU | inst_BGE) &  greater_equal_flag);


  assign pc_next = (reset_valid) ? 32'b0 :
                      (branch_in_effect) ? program_counter + i_imm_B :
                      (inst_JALR) ? rs1_plus_imm_i :
                      (inst_JAL) ? program_counter + i_imm_J : 
                      (inst_MRET) ? csr_mepc : pc_plus_four;  


  /**
   *  LOAD/STORE
   */

  wire opcode_LOAD = (opcode == 7'b0000011);
  wire opcode_STORE = (opcode == 7'b0100011);

  wire inst_LW = opcode_LOAD & (funct3 == 3'b010);
  wire inst_SW = opcode_STORE & (funct3 == 3'b010);
  wire inst_SB = opcode_STORE & (funct3 == 3'b000);
  wire inst_SH = opcode_STORE & (funct3 == 3'b001);
  wire inst_LB = opcode_LOAD & (funct3 == 3'b000);
  wire inst_LH = opcode_LOAD & (funct3 == 3'b001);
  wire inst_LBU = opcode_LOAD & (funct3 == 3'b100);
  wire inst_LHU = opcode_LOAD & (funct3 == 3'b101);

  //addr: 

  wire [31:0] loadstore_addr = (opcode_LOAD) ? rs1_plus_imm_i :
                               (opcode_STORE) ? rf_rs1data + i_imm_S : 32'b0;

  assign core_loadstore_ce = (opcode_LOAD | opcode_STORE);
//  assign core_loadstore_we = opcode_STORE;
  assign core_loadstore_we = (halt_valid) ? 1'b0 : opcode_STORE;

  assign core_loadstore_addr = loadstore_addr; //loadstore_addr is in bytes
  //output data from rs2
  assign core_loadstore_data_out = (loadstore_addr[1:0] == 2'b11) ? {rf_rs2data[7:0], 24'h5a6a7a} : 
           (loadstore_addr[1:0] == 2'b10) ? {rf_rs2data[15:0], 16'h6a7a} : 
           (loadstore_addr[1:0] == 2'b01) ? {rf_rs2data[23:0], 8'h7a} : rf_rs2data;


  //write mask
  wire [3:0] SW_mask = (loadstore_addr[1:0] == 2'b11) ? 4'b1000 : 
           (loadstore_addr[1:0] == 2'b10) ? 4'b1100 : 
           (loadstore_addr[1:0] == 2'b01) ? 4'b1110 : 4'b1111;
  wire [3:0] SH_mask = (loadstore_addr[1:0] == 2'b11) ? 4'b1000 : 
           (loadstore_addr[1:0] == 2'b10) ? 4'b1100 : 
           (loadstore_addr[1:0] == 2'b01) ? 4'b0110 : 4'b0011;
  wire [3:0] SB_mask = (loadstore_addr[1:0] == 2'b11) ? 4'b1000 : 
            (loadstore_addr[1:0] == 2'b10) ? 4'b0100 : 
            (loadstore_addr[1:0] == 2'b01) ? 4'b0010 : 4'b0001;

  assign core_loadstore_wmask = ({4{inst_SW}} & SW_mask) 
                       | ({4{inst_SH}} & SH_mask) 
                       | ({4{inst_SB}} & SB_mask); 


  wire [7:0] load_byte_data = (loadstore_addr[1:0] == 2'b11) ? core_loadstore_data_in[31:24] :
         (loadstore_addr[1:0] == 2'b10) ? core_loadstore_data_in[23:16] :
         (loadstore_addr[1:0] == 2'b01) ? core_loadstore_data_in[15:8] : core_loadstore_data_in[7:0];
  wire [31:0] load_byte_result = ({32{inst_LB}} & {{24{load_byte_data[7]}} , load_byte_data}) 
                     | ({32{inst_LBU}} & {24'b0 , load_byte_data});

  wire [15:0] load_half_data = (loadstore_addr[1:0] == 2'b11) ? {8'h4a, core_loadstore_data_in[31:24]} :
              (loadstore_addr[1:0] == 2'b10) ? core_loadstore_data_in[31:16] :
              (loadstore_addr[1:0] == 2'b01) ? core_loadstore_data_in[23:8] :
                   core_loadstore_data_in[15:0];
  wire [31:0] load_half_result = ({32{inst_LH}} & {{16{load_half_data[15]}} , load_half_data}) 
                     | ({32{inst_LHU}} & {16'b0 , load_half_data});

  wire [31:0] load_word_data = (loadstore_addr[1:0] == 2'b11) ? {24'h4a5a6a, core_loadstore_data_in[31:24]} :
              (loadstore_addr[1:0] == 2'b10) ? {16'h4a5a, core_loadstore_data_in[31:16]} :
              (loadstore_addr[1:0] == 2'b01) ? {8'h4a, core_loadstore_data_in[31:8]} :
                   core_loadstore_data_in;
  wire [31:0] load_word_result = ({32{inst_LW}} & load_word_data );

  wire [31:0] load_result = load_word_result | load_half_result | load_byte_result;


  /**
   *  CSR
   */
  //opcode=SYSTEM
  //csr = funct12
  //rs1, funct3, rd
  wire inst_group_CSR = opcode_SYSTEM & (funct3[1:0] != 2'b00);

  wire csr_write_csr_flag = inst_group_CSR & (!((funct3[1:0] != 2'b01) & (i_rs1_idx == 3'b000)));
  wire csr_update_rd_flag = inst_group_CSR & (i_rd_idx != 3'b000);

  wire [31:0] csr_orig_data; //selected csr value
  wire [31:0] csr_src_data = (!inst_group_CSR) ? 32'b0 :
                           (funct3[2] == 1'b0) ? rf_rs1data : {27'b0, i_rs1_idx};
  wire [31:0] csr_src_data_mask = csr_src_data & 32'h001f;

  wire [31:0] csr_csrrw_data = (funct3[1:0] == 2'b01) ? csr_src_data : 32'b0;
  wire [31:0] csr_csrrs_data = (funct3[1:0] == 2'b10) ? (csr_orig_data | csr_src_data_mask) : 32'b0;
  wire [31:0] csr_csrrc_data = (funct3[1:0] == 2'b11) ? (csr_orig_data & (~csr_src_data_mask)) : 32'b0;

  wire [31:0] csr_csrnew_data = csr_csrrw_data | csr_csrrs_data | csr_csrrc_data;


//implemented csr list:
//csr_idx, name
//0x300 mstatus
//0x304 mie 
//0x305 mtvec 
//0x340 mscratch
//0x341 mepc
//0x342 mcause
//0x344 mip
//
//custom csr:
//0x7c0 mtime
//0x7c1 mtimecmp
//0x7c2 mtimectrl
//0x7c3 mswctrl
//0x7c6 e_csr0

  wire [11:0] csr_csridx = funct12;
  wire csr_en = inst_group_CSR;

  //implement one extended csr reg first: 0x7c6 e_csr0
  //
  wire csr_e_csr0_en = csr_en & (csr_csridx == 12'h7c6);

  reg [31:0] csr_e_csr0;

  //update csr_e_csr0
  always @(posedge clk) begin
    if (csr_e_csr0_en & csr_write_csr_flag)
      csr_e_csr0 <= csr_csrnew_data;
  end

  assign o_ext_csr0 = csr_e_csr0;

  //implerment mtimer
  //
  reg [31:0] csr_mtime, csr_mtimecmp;
  reg [3:0] csr_mtimectrl;
  wire csr_mtime_en = csr_en & (csr_csridx == 12'h7c0);
  wire csr_mtimecmp_en = csr_en & (csr_csridx == 12'h7c1);
  wire csr_mtimectrl_en = csr_en & (csr_csridx == 12'h7c2);

  //update mtime
  always @(posedge clk) begin
    if (~reset_n) begin
        csr_mtime <= 32'b0;
    end
    else if (csr_mtime_en & csr_write_csr_flag) begin
      csr_mtime <= csr_csrnew_data;
    end
    else if (csr_mtimectrl[0]) begin
      if (csr_mtime >= csr_mtimecmp) begin
        csr_mtime <= 32'b0;
      end
      else begin
        csr_mtime <= csr_mtime + 1'b1;
      end
    end
  end

  //update mtimecmp
  always @(posedge clk) begin
    if (~reset_n) begin
        csr_mtimecmp <= 32'h03;
    end
    else if (csr_mtimecmp_en & csr_write_csr_flag) begin
      csr_mtimecmp <= csr_csrnew_data;
    end
  end

  //update mtimectrl
  always @(posedge clk) begin
    if (~reset_n) begin
        csr_mtimectrl <= 4'b0;
    end
    else if (csr_mtimectrl_en & csr_write_csr_flag) begin
      csr_mtimectrl <= csr_csrnew_data[3:0];
    end
  end

  //overflow_status logic
  reg csr_mtime_overflow;
  always @(posedge clk) begin
    if (~reset_n) begin
        csr_mtime_overflow <= 1'b0;
    end
    else if ((csr_mtime >= csr_mtimecmp) && csr_mtimectrl[0]) begin //set overflow
        csr_mtime_overflow <= 1'b1;
    end
    else if (csr_mtimectrl_en) begin //read or write mtimectrl can clear overflow flag
        csr_mtime_overflow <= 1'b0;
    end 
  end

  //software interrupt
  //
  reg [3:0] csr_mswctrl;
  wire csr_mswctrl_en = csr_en & (csr_csridx == 12'h7c3);

  always @(posedge clk) begin
    if (~reset_n) begin
        csr_mswctrl <= 4'b0;
    end
    else if (csr_mswctrl_en & csr_write_csr_flag) begin
      csr_mswctrl <= csr_csrnew_data[3:0];
    end
  end

  wire csr_sw_int_flag = csr_mswctrl[0];


  //mepc
  //* mepc is update in interrupt FSM with interrupt breakpoint
  //* and it should be allowed to be written with csr instruction
  //* to change the mret return point for RTOS task switching.
  //
  reg [31:0] csr_mepc;
  wire csr_mepc_en = csr_en & (csr_csridx == 12'h341);

  wire csr_ctrl_int_triggered;

  always @(posedge clk or negedge reset_n) begin
    if (!reset_n) begin
      csr_mepc <= 32'b0;
    end
    else begin
      if (csr_ctrl_int_triggered) begin
        csr_mepc <= pc_next;
      end 
      else if (csr_mepc_en & csr_write_csr_flag) begin
        csr_mepc <= csr_csrnew_data;
      end
    end
  end

  //mtvec
  //
  reg [31:0] csr_mtvec;
  wire csr_mtvec_en = csr_en & (csr_csridx == 12'h305);

  always @(posedge clk or negedge reset_n) begin
    if (!reset_n) begin
      csr_mtvec <= 32'h80002e00;
    end else begin
      if (csr_mtvec_en & csr_write_csr_flag)
        csr_mtvec <= csr_csrnew_data;
    end
  end

  //mscratch
  //
  reg [31:0] csr_mscratch;
  wire csr_mscratch_en = csr_en & (csr_csridx == 12'h340);

  always @(posedge clk or negedge reset_n) begin
    if (!reset_n) begin
      csr_mscratch <= 32'b0;
    end else begin
      if (csr_mscratch_en & csr_write_csr_flag)
        csr_mscratch <= csr_csrnew_data;
    end
  end

  //mstatus
  //
  reg csr_mstatus_mie; //bit3
  reg csr_mstatus_mpie; //bit7, for save of mie when interrupt
  //bit12:11, mpp=2'b11

  wire csr_mstatus_en = csr_en & (csr_csridx == 12'h300);

  wire csr_ctrl_int_mret;
  always @(posedge clk or negedge reset_n) begin
    if (!reset_n) begin
      csr_mstatus_mie <= 1'b0;
    end else begin
      if (csr_mstatus_en & csr_write_csr_flag) begin
        csr_mstatus_mie <= csr_csrnew_data[3];
        csr_mstatus_mpie <= csr_csrnew_data[7];
      end else if (csr_ctrl_int_triggered) begin
        csr_mstatus_mpie = csr_mstatus_mie;
        csr_mstatus_mie = 1'b0;
      end else if (inst_MRET) begin
        csr_mstatus_mie <= csr_mstatus_mpie;
      end
    end
  end

  wire [31:0] csr_mstatus = {19'b0, 2'b11, 3'b0, csr_mstatus_mpie, 
                        3'b0, csr_mstatus_mie, 3'b0};

  //mie
  //
  reg [31:0] csr_mie;
  wire csr_mie_en = csr_en & (csr_csridx == 12'h304);

  always @(posedge clk or negedge reset_n) begin
    if (!reset_n) begin
      csr_mie <= 32'b0;
    end else begin
      if (csr_mie_en & csr_write_csr_flag)
        csr_mie <= csr_csrnew_data;
    end
  end

  //mip
  //interrupt FSM is triggered by clk_ctrl_b
  //csr_mip_r is alse latched by clk_ctrl_b and cleared by software read
  wire [31:0] csr_mip;
  wire csr_mip_en = csr_en & (csr_csridx == 12'h344);
  wire csr_mip_ext_int = csr_mstatus_mie & csr_mie[11] & ext_int_req_i;
  wire csr_mip_time_int = csr_mstatus_mie & csr_mie[7] & csr_mtime_overflow;
  wire csr_mip_sw_int = csr_mstatus_mie & csr_mie[3] & csr_sw_int_flag;

  assign csr_mip = {20'b0, csr_mip_ext_int, 3'b0, csr_mip_time_int, 3'b0, 
                 csr_mip_sw_int , 3'b0};
  //ext_int is used in interrupt FSM
  assign ext_int = !(csr_mip == 32'b0);


  //mcause
  //mcause is read only by csr instructions.
  //mcause is set via interrupt FSM logic.
  //
  reg [31:0] csr_mcause;
  wire csr_mcause_en = csr_en & (csr_csridx == 12'h342);


  //set csr_orig_data for rd write back and new csr value calculation

  wire [31:0] csr_orig_data1, csr_orig_data2;

  assign csr_orig_data1 = (csr_mepc_en) ? csr_mepc : 
                         (csr_mstatus_en) ? csr_mstatus : 
                         (csr_mtvec_en) ? csr_mtvec : 
                         (csr_mcause_en) ? csr_mcause :
                         (csr_mie_en) ? csr_mie :
                         (csr_mip_en) ? csr_mip : 32'b0;

  assign csr_orig_data2 = (csr_mscratch_en) ? csr_mscratch :
                         (csr_mtime_en) ? csr_mtime : 
                         (csr_mtimecmp_en) ? csr_mtimecmp : 
                         (csr_mtimectrl_en) ? csr_mtimectrl : 
                         (csr_mswctrl_en) ? csr_mswctrl : 
                         (csr_e_csr0_en) ? csr_e_csr0 : 32'b0;

  assign csr_orig_data = csr_orig_data1 | csr_orig_data2;

  /**
   *  write back
   */

  //rd source:
  // 1) from ALU result;
  // 2) from LOAD result;
  // 3) from JAL, save PC;
  // 4) from LUI;
  // 5) from csr;

  wire inst_JAL = opcode_JAL;
  wire inst_J = inst_JAL & (i_rd_idx == 5'b0);
  wire inst_LUI = (opcode == 7'b0110111);
  wire inst_JALR = (opcode == 7'b1100111);
  wire inst_AUIPC = (opcode == 7'b0010111);
  
  wire link_flag = (inst_JAL | inst_JALR) &(i_rd_idx != 5'b0);
  wire [31:0] pc_plus_four = program_counter + 4; 
  wire [31:0] pc_plus_imm_U = program_counter + i_imm_U;



  assign rf_rd_data = (opcode_OP_IMM | opcode_OP) ? alu_result :
                          (opcode_LOAD) ? load_result : 
                          (inst_AUIPC) ? pc_plus_imm_U :
                          (inst_LUI) ? i_imm_U : 
                          (link_flag) ? pc_plus_four : 
                          (csr_update_rd_flag) ? csr_orig_data : 32'b0;
  assign rf_rdwen = opcode_OP_IMM | opcode_OP | link_flag | 
                     opcode_LOAD | inst_AUIPC | inst_LUI | csr_update_rd_flag;


  /**
   *  interrupt
   */
//use FSM for implementation
//
  reg ext_int_in_effect;


  //FSM section 1
  localparam S_INT_IDLE = 0, S_INT_TRIGGERED = 1, S_INT_ACTIVE=2, S_INT_MRET=3;
  reg [1:0] current_state = 0, next_state;    

  always @(negedge clk or negedge reset_n) begin
    if (!reset_n) begin
      current_state <= S_INT_IDLE;
    end else begin
      current_state <= next_state;
    end
  end

  //FSM section 2

  //ctrl signal
  reg ctrl_int_triggered;
  reg ctrl_int_mret;

  always @(*) begin
    next_state = 0;
    ctrl_int_triggered = 0;
    ctrl_int_mret = 0;

    case (current_state) 
      S_INT_IDLE: begin
        if (ext_int) begin  
          next_state <= S_INT_TRIGGERED;
        end else begin
          next_state <= S_INT_IDLE;                        
        end
      end
      S_INT_TRIGGERED : begin
        ctrl_int_triggered = 1'b1;
        next_state <= S_INT_ACTIVE;
      end
      S_INT_ACTIVE : begin
        if (inst_MRET) begin
          next_state <= S_INT_MRET;
        end else begin
          next_state <= S_INT_ACTIVE;
        end
      end
      S_INT_MRET : begin
        ctrl_int_mret = 1'b1;
        next_state <= S_INT_IDLE;
      end
      default: begin
        next_state <= S_INT_IDLE;
      end
    endcase
  end

  //FSM section 3
  always @(posedge clk) begin
    if (ctrl_int_triggered) begin
      if (csr_mip_ext_int)
        csr_mcause <= 32'h8000000b;
      else if (csr_mip_time_int)
        csr_mcause <= 32'h80000007;
      else if (csr_mip_sw_int)
        csr_mcause <= 32'h80000003;
      else
        csr_mcause <= 32'h80000000;
    end
  end

  assign csr_ctrl_int_triggered = ctrl_int_triggered;
  assign csr_ctrl_int_mret = ctrl_int_mret;


  wire [9:0]  isr_offset = (csr_mcause[31]) ? {csr_mcause[7:0],2'b0} : 10'b0;
  wire [31:0]  isr_entrance = (csr_mtvec[1:0] == 2'b0) ? csr_mtvec :
      csr_mtvec + isr_offset;
  assign pc_next_with_int = (ctrl_int_triggered) ? isr_entrance : pc_next;


 assign int_active = (current_state != S_INT_IDLE);



  //monitor_port: for debug
  assign monitor_port = x16_value;

endmodule

